package com.rsscollector.util;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.apache.log4j.Logger;
import org.compass.core.Compass;
import org.compass.core.config.CompassConfiguration;
import org.compass.core.config.CompassConfigurationFactory;
import org.compass.gps.CompassGps;
import org.compass.gps.device.jpa.JpaGpsDevice;
import org.compass.gps.impl.SingleCompassGps;

public class PersistenceUtil {
	
	private static Logger logger = Logger.getLogger(PersistenceUtil.class.getName());


    private static final ThreadLocal<EntityManager> entityManagerThreadLocal = new ThreadLocal<EntityManager>();
    private static final ThreadLocal<Long> transactionNumberThreadLocal = new ThreadLocal<Long>();
    private static EntityManagerFactory emf;

    /**
     * Compass API
     */
    private static Compass compass;
    private static CompassGps gps;
    
    public static void init() {
        // Init JPA
    	if (emf == null) {
            emf = Persistence.createEntityManagerFactory("manager1");
        }
        
    	// Init Compass
        if (compass == null) {
        	CompassConfiguration conf = CompassConfigurationFactory.newConfiguration();
        	conf.configure();
            compass = conf.buildCompass();
        }
        // Init Compass JPA Device (to synchronize JPA source --> Compass index)
        if (gps == null) {
	        gps = new SingleCompassGps(compass);
	        JpaGpsDevice jpaDevice = new JpaGpsDevice("jpa", emf);
	        jpaDevice.setInjectEntityLifecycleListener(true);
	        jpaDevice.setMirrorDataChanges(true);
	        gps.addGpsDevice(jpaDevice);
	        gps.start();
        }
    }

    public static void destroy() {
    	if (gps != null) {
    		gps.stop();
        	gps = null;
    	}
    	if (compass != null) {
	        compass.close();
	        compass = null;
    	}
    	if (emf != null) {
	        emf.close();
	        emf = null;
    	}
    }

    public static void begin() {
        init();
        EntityManager em = emf.createEntityManager();
        entityManagerThreadLocal.set(em);
        transactionNumberThreadLocal.set(Long.valueOf(0));
    }

    public static void end() {
        EntityManager em = entityManagerThreadLocal.get();
        if (em != null) {
            if (em.isOpen()) {
                if (em.getTransaction().isActive()) {
                    try {
                    	logger.debug("Commiting transaction");
                        em.getTransaction().commit();
                    } catch (Exception e) {
                        logger.debug("Error commiting transaction", e);
                        try {
                            em.getTransaction().rollback();
                        } catch (Exception e2) {
                        	logger.debug("Error rolling back", e2);
                        }
                        throw new RuntimeException(e);
                    }
                }
                em.close();
            }
            entityManagerThreadLocal.remove();
        }
        Long transactionNumber = transactionNumberThreadLocal.get();
        if (transactionNumber != null) {
            transactionNumberThreadLocal.remove();
        }
    }

    public static void endWithError() {
        EntityManager em = entityManagerThreadLocal.get();
        if (em != null) {
            if (em.isOpen()) {
                if (em.getTransaction().isActive()) {
                    try {
                    	logger.debug("Rolling back transaction");
                        em.getTransaction().rollback();
                    } catch (Exception e) {
                    	logger.debug("Error rolling back transaction", e);
                    }
                }
                try {
                	em.close();
                } catch (Exception e) {
                	logger.debug("Error closing entity manager", e);
				}
            }
            entityManagerThreadLocal.remove();
        }
        Long transactionNumber = transactionNumberThreadLocal.get();
        if (transactionNumber != null) {
            transactionNumberThreadLocal.remove();
        }
    }
    
    public static EntityManager getEntityManager() {
        EntityManager em = entityManagerThreadLocal.get();
        return em;
    }

    public static Compass getCompass() {
        return compass;
    }
    
    public static CompassGps getCompassGps() {
        return gps;
    }
    
    public static EntityManager getEntityManagerWithTX() {
        EntityManager em = entityManagerThreadLocal.get();
        if (!em.getTransaction().isActive()) em.getTransaction().begin();
        Long transactionNumber = transactionNumberThreadLocal.get();
        transactionNumberThreadLocal.set(Long.valueOf(transactionNumber.longValue() + 1));
        return em;
    }

    public static void endTransaction() {
        Long transactionNumber = transactionNumberThreadLocal.get();
        transactionNumberThreadLocal.set(Long.valueOf(transactionNumber.longValue() - 1));
        if (transactionNumber.longValue() == 1) {
            // commit
            EntityManager em = entityManagerThreadLocal.get();
            if (em != null) {
                if (em.isOpen()) {
                    if (em.getTransaction().isActive()) {
                        em.getTransaction().commit();
                    }
                }
            }
        }
    }

}
